/*
 ***************************************************************************************
 * EP for web developers.Supported By Junxworks
 * @Title:  ExeContext.java   
 * @Package io.github.junxworks.qt.modules.mm.engine   
 * @Description: (用一句话描述该文件做什么)   
 * @author: Administrator
 * @date:   2022年10月16日 上午9:01:01   
 * @version V1.0 
 * @Copyright: 2022 Junxworks. All rights reserved. 
 * 注意：
 *  ---------------------------------------------------------------------------------- 
 * 文件修改记录
 *     文件版本：         修改人：             修改原因：
 ***************************************************************************************
 */
package io.github.junxworks.qt.modules.mm.engine;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import io.github.junxworks.junx.core.exception.BaseRuntimeException;
import io.github.junxworks.junx.core.lang.Initializable;
import io.github.junxworks.junx.core.util.ClockUtils;
import io.github.junxworks.junx.core.util.ClockUtils.Clock;
import io.github.junxworks.junx.core.util.DateUtils;
import io.github.junxworks.junx.core.util.StringUtils;
import io.github.junxworks.junx.event.EventBus;
import io.github.junxworks.qt.modules.mm.engine.cache.MemoryStatCache;
import io.github.junxworks.qt.modules.mm.engine.cache.StatCache;
import io.github.junxworks.qt.modules.mm.vo.MmExpressionVo;
import io.github.junxworks.qt.modules.mm.vo.MmModelVo;
import io.github.junxworks.qt.modules.mm.vo.MmVariableVo;

// TODO: Auto-generated Javadoc
/**
 * 执行上下文.
 */
public class ExeContext implements Initializable {

	/**  全局唯一ID，前端请求带参数过来. */
	private String UID = UUID.randomUUID().toString();

	private Clock clock = ClockUtils.createClock();

	/** 超过12个小时的上下文会被清除. */
	private static Long deadTime = 12 * 60 * 60 * 1000l;

	/** 是否已经初始化. */
	private boolean initialized = false;

	/** 是否繁忙 */
	private AtomicBoolean busy = new AtomicBoolean(false);

	/** 是否只单独跑统计数据 */
	private boolean onlyStat = false;

	/** 交易日期yyyyMMdd. */
	private String tradeDate;

	/** 执行日期yyyyMMdd. */
	private String execDate;

	/** 指定执行的股票名称，多个用逗号隔开. */
	private String stockCodes;

	private String tsCodesCondition;

	private Set<String> keywords = Sets.newHashSet();

	private List<String> tsCodes;

	private List<String> industry;

	/** 需要执行的模型. */
	private List<String> modelNos;

	private List<String> statNames;

	private List<Datastream> dataStreams = Lists.newArrayList();

	private List<MmVariableVo> variables;

	private Map<String, List<MmVariableVo>> variableMap;

	private List<MmExpressionVo> expressions;

	private Map<String, Datastream> dataStreamMap;

	private Map<String, MmModelVo> modelMap;

	private Map<String, List<MmExpressionVo>> expressionMap;

	private EventBus eventBus;

	private CountDownLatch counter;

	//统计缓存
	private StatCache statCache;

	/** 被抛弃的股票，一般来说执行期间发生异常，数据缺失等会直接抛弃该股票执行. */
	private Set<String> abandonedStocks = Sets.newConcurrentHashSet();

	/**************************************计算后的值，模型执行完成后丢弃*************************************************/

	private Map<String, Object> statValues = Maps.newConcurrentMap();

	private Map<String, Object> expressionResults = Maps.newConcurrentMap();

	/*************************************************************************************************/

	public Map<String, MmModelVo> getModelMap() {
		return modelMap;
	}

	public List<String> getIndustry() {
		return industry;
	}

	public void setIndustry(List<String> industry) {
		this.industry = industry;
	}

	public Set<String> getKeywords() {
		return keywords;
	}

	public List<String> getStatNames() {
		return statNames;
	}

	public void setStatNames(List<String> statNames) {
		this.statNames = statNames;
	}

	public boolean isOnlyStat() {
		return onlyStat;
	}

	public void setOnlyStat(boolean onlyStat) {
		this.onlyStat = onlyStat;
	}

	public void setModelMap(Map<String, MmModelVo> modelMap) {
		this.modelMap = modelMap;
	}

	public Map<String, List<MmExpressionVo>> getExpressionMap() {
		return expressionMap;
	}

	public void setExpressionMap(Map<String, List<MmExpressionVo>> expressionMap) {
		this.expressionMap = expressionMap;
	}

	public Map<String, Object> getExpressionResults() {
		return expressionResults;
	}

	public void putExpressionResults(String key, Object value) {
		expressionResults.put(key, value);
	}

	public Object getExpressionResult(String key) {
		return expressionResults.get(key);
	}

	public Map<String, Object> getStatValues() {
		return statValues;
	}

	public void putStatValue(String key, Object value) {
		statValues.put(key, value);
	}

	public Object getStatValue(String key) {
		return statValues.get(key);
	}

	public void clearValues() {
		statValues.clear();
		expressionResults.clear();
	}

	public boolean isAbandoned(String stock) {
		return abandonedStocks.contains(stock);
	}

	public void abandonStock(String stock) {
		abandonedStocks.add(stock);
	}

	public StatCache getStatCache() {
		return statCache;
	}

	public List<MmExpressionVo> getExpressions() {
		return expressions;
	}

	public void setExpressions(List<MmExpressionVo> expressions) {
		this.expressions = expressions;
	}

	public List<MmVariableVo> getVariables() {
		return variables;
	}

	public void setVariables(List<MmVariableVo> variables) {
		this.variables = variables;
		this.variableMap = Maps.newHashMap();
		for (MmVariableVo variable : variables) {
			String keyword = variable.getKeyword();
			List<MmVariableVo> sub = null;
			if (variableMap.containsKey(keyword)) {
				sub = variableMap.get(keyword);
			} else {
				sub = Lists.newArrayList();
				variableMap.put(keyword, sub);
			}
			sub.add(variable);
		}
		variableMap.values().forEach(v -> {
			v.sort(new Comparator<MmVariableVo>() {
				@Override
				public int compare(MmVariableVo o1, MmVariableVo o2) {
					return o1.getPriority() > o2.getPriority() ? 1 : 0;
				}
			});
		});
	}

	public List<MmVariableVo> getVariables(String keyword) {
		return variableMap.get(keyword);
	}

	public EventBus getEventBus() {
		return eventBus;
	}

	public void setEventBus(EventBus eventBus) {
		this.eventBus = eventBus;
	}

	/**
	 *  是否执行表达式与模型，因为统计指标需要一段时间积累数据，完成过后才能开始跑表达式和模型.
	 *
	 * @return true, if successful
	 */
	public boolean shouldExecuteExp() {
		return !StringUtils.isNull(tradeDate) && !StringUtils.isNull(execDate) && (DateUtils.parse(tradeDate, "yyyyMMdd").getTime() >= DateUtils.parse(execDate, "yyyyMMdd").getTime());
	}

	public boolean isDead() {
		return clock.countMillis() >= deadTime;
	}

	public boolean isBusy() {
		return busy.get();
	}

	public String getTsCodesCondition() {
		return tsCodesCondition;
	}

	public List<String> getTsCodes() {
		return tsCodes;
	}

	public void setTsCodes(List<String> tsCodes) {
		this.tsCodes = tsCodes;
	}

	public boolean work() {
		return this.busy.compareAndSet(false, true);
	}

	public boolean done() {
		dataStreams.forEach(ds -> {
			try {
				ds.destroy();
			} catch (Exception e) {
			}
		});
		clearValues(); //清空之前统计结果，当日重新统计
		return this.busy.compareAndSet(true, false);
	}

	public List<Datastream> getDataStreams() {
		return dataStreams;
	}

	/**
	 * Gets the uid.
	 *
	 * @return the uid
	 */
	public String getUID() {
		return UID;
	}

	public void setUID(String uID) {
		UID = uID;
	}

	/**
	 * Checks if is initialized.
	 *
	 * @return true, if is initialized
	 */
	public boolean isInitialized() {
		return initialized;
	}

	/**
	 * Sets the initialized.
	 *
	 * @param initialized the new initialized
	 */
	public void setInitialized(boolean initialized) {
		this.initialized = initialized;
	}

	/**
	 * Gets the trade date.
	 *
	 * @return the trade date
	 */
	public String getTradeDate() {
		return tradeDate;
	}

	/**
	 * Sets the trade date.
	 *
	 * @param tradeDate the new trade date
	 */
	public void setTradeDate(String tradeDate) {
		this.tradeDate = tradeDate;
	}

	/**
	 * Gets the exec date.
	 *
	 * @return the exec date
	 */
	public String getExecDate() {
		return execDate;
	}

	/**
	 * Sets the exec date.
	 *
	 * @param execDate the new exec date
	 */
	public void setExecDate(String execDate) {
		this.execDate = execDate;
	}

	public String getStockCodes() {
		return stockCodes;
	}

	public void setStockCodes(String stockCodes) {
		if (StringUtils.notNull(stockCodes)) {
			this.stockCodes = stockCodes;
			String[] codes = stockCodes.split(",");
			this.tsCodes = Lists.newArrayList(codes);
			this.tsCodes.sort(new Comparator<String>() {

				@Override
				public int compare(String o1, String o2) {
					return o1.compareTo(o2);
				}

			});

			StringBuilder sb = new StringBuilder();
			for (String s : this.tsCodes) {
				if (sb.length() > 0) {
					sb.append(",");
				}
				sb.append("'").append(s).append("'");
			}
			this.tsCodesCondition = sb.toString();
		}
	}

	/**
	 * Gets the model nos.
	 *
	 * @return the model nos
	 */
	public List<String> getModelNos() {
		return modelNos;
	}

	/**
	 * Sets the model nos.
	 *
	 * @param modelNos the new model nos
	 */
	public void setModelNos(List<String> modelNos) {
		this.modelNos = modelNos;
	}

	public void addDatastream(Datastream ds) {
		this.dataStreams.add(ds);
	}

	public void addAllDatastream(List<Datastream> dataStreams) {
		this.dataStreams.addAll(dataStreams);
		dataStreams.forEach(ds -> {
			this.keywords.add(ds.getKeyColumn());
		});
	}

	public Clock getClock() {
		return clock;
	}

	public Datastream getDataStream(String dsName) {
		return dataStreamMap.get(dsName);
	}

	@Override
	public void initialize() throws Exception {
		if (!initialized) {
			this.dataStreams.stream().forEach(ds -> {
				ds.setCtx(ExeContext.this);
			});
			dataStreamMap = dataStreams.stream().collect(Collectors.toMap(Datastream::getDsName, v -> v));
			if (this.getTsCodes().isEmpty()) {
				throw new BaseRuntimeException("没有需要执行的股票");
			}
			counter = new CountDownLatch(getTsCodes().size());
			statCache = new MemoryStatCache();
			this.initialized = true;
		} else {
			counter = new CountDownLatch(getTsCodes().size() - abandonedStocks.size());
		}
	}

	public CountDownLatch getCounter() {
		return counter;
	}

	public void setCounter(CountDownLatch counter) {
		this.counter = counter;
	}

	public void countDown() {
		this.counter.countDown();
	}

	@Override
	public void destroy() throws Exception {
		this.initialized = false;
		statCache = null;
		counter = null;
		dataStreamMap.clear();
		dataStreams.forEach(ds -> {
			try {
				ds.destroy();
			} catch (Exception e) {
			}
		});
		clearValues();
		dataStreams.clear();
		variables.clear();
		expressions.clear();
		modelMap.clear();
		expressionMap.clear();
		abandonedStocks.clear();
	}

}
